#pragma once

#include <vector>
#include <string>
#include <algorithm>

enum DIRECTION : int {
    NONE,
    RIGHT,
    DOWN,
    LEFT,
    UP
};

struct FigurePoint {
    int x;
    int y;
    DIRECTION direction;
    FigurePoint & move(){
        switch(direction){
            case UP:    y--; break;
            case LEFT:  x--; break;
            case DOWN:  y++; break;
            case RIGHT: x++; break;
            case NONE:;
        }
        return *this;
    }
    FigurePoint & rotate(bool counterClockWise){
        switch(direction){
            case NONE:
            case UP:
                direction = counterClockWise ? LEFT : RIGHT; break;
            case LEFT:
                direction = counterClockWise ? DOWN : UP;  break;
            case DOWN:
                direction = counterClockWise ? RIGHT : LEFT; break;
            case RIGHT:
                direction = counterClockWise ? UP : DOWN; break;
        }
        return *this;
    }
};

using Data = std::vector<std::string>;
using Figure = std::vector<FigurePoint>;

std::string join(const std::string &sep, const Data &to_join) {
    auto ItBegin = to_join.cbegin();
    auto ItEnd = to_join.cend();
    std::string result = *ItBegin++;
    for (; ItBegin != ItEnd; ++ItBegin ) {
        result.insert(result.cend(), sep.begin(), sep.end());
        result.insert(result.cend(), ItBegin->begin(), ItBegin->end());
    }
    return result;
}

Data split_lines(const std::string &to_split) {
    Data result;
    constexpr char separator = '\n';
    auto ItBegin = to_split.begin();
    auto ItEnd = to_split.end();
    auto ItSplit = std::find(ItBegin,  ItEnd,  separator);
    while ( ItBegin < ItEnd ) {
        result.emplace_back(ItBegin, ItSplit);
        ItBegin = std::next(ItSplit);
        ItSplit = std::find(ItBegin,  ItEnd,  separator);
    }
    return result;
}

namespace worker {
    DIRECTION rotateCheck(FigurePoint fp, const Data & data){
        if( data.at( fp.y).at( fp.x) == '+' ){
            FigurePoint clockWise = fp;
            FigurePoint counterClockWise = fp;
            fp.move();
            clockWise.rotate(false).move();
            counterClockWise.rotate(true).move();
            if( clockWise.x >= 0 && clockWise.y >= 0 && clockWise.y < data.size() && clockWise.x < data.at(clockWise.y).size() && data.at(clockWise.y).at(clockWise.x) != ' ' ){
                return clockWise.direction;
            } else if( fp.x >= 0 && fp.y >= 0 && fp.y < data.size() && fp.x < data.at(fp.y).size() && data.at(fp.y).at(fp.x) != ' '){
                return NONE;
            } else if( counterClockWise.x >= 0 && counterClockWise.y >= 0 && counterClockWise.y < data.size() && counterClockWise.x < data.at(counterClockWise.y).size() && data.at(counterClockWise.y).at(counterClockWise.x) != ' ' ){
                return counterClockWise.direction;
            }
        }
        return NONE;
    }

    Figure extract(const Data & data, int nRow, int nColumn){
        Figure f;
        FigurePoint fp{nColumn, nRow, DIRECTION::NONE};
        DIRECTION d;
        while(true){
            d = rotateCheck(fp, data);
            if( d != NONE ){
                f.push_back(fp);
                fp.direction = d;
            }
            fp.move();
            if( fp.y == f[0].y && fp.x == f[0].x ){
                break;
            }
        }
        return f;
    }
    
    std::vector<Figure> checkFigures(const std::vector<Figure> & figures){
        std::vector<Figure> validFigures;
        for( auto it = figures.rbegin(); it != figures.rend(); ++it ){
            bool valid = true;
            for( const auto & point : *it ){
                if( point.y < it->at(0).y ){
                    valid = false;
                    break;
                }
            }
            if( valid ){
                for(auto check = std::next(it); check != figures.rend(); ++check){
                    if( it->size() == check->size() ){
                        int idx = 0;
                        for( ; idx < check->size(); ++idx ){
                            if( it->at(0).x == check->at(idx).x
                                && it->at(0).y == check->at(idx).y )
                                break;
                        }
                        int count = 0;
                        for( int k = 0; idx < check->size(); ++idx, ++k ){
                            if( it->at(k).x == check->at(idx).x && it->at(k).y == check->at(idx).y )
                                count++;
                            else
                                count = 0;
                        }
                        if( count > 2 ){
                            valid = false;
                        }
                    }
                }
            }
            if( valid ){
                validFigures.push_back(*it);
            }
        }
        return validFigures;
    }

    std::string toASCII(Figure f){
        f.push_back({f.at(0).x, f.at(0).y, UP});
        FigurePoint min = f.at(0), max = f.at(0);
        for( const auto & point : f ){
            if( point.x > max.x ) max.x = point.x;
            if( point.y > max.y ) max.y = point.y;
            if( point.x < min.x ) min.x = point.x;
            if( point.y < min.y ) min.y = point.y;
        }
        for( auto it = f.begin(); it != f.end(); ++it){
            it->y -= min.y;
            it->x -= min.x;
            if( it != std::prev(f.end()) ){
                it->direction = std::next(it)->direction;
            }
        }
        Data data(max.y - min.y + 1, std::string(max.x-min.x +1, ' '));
        for( auto it = f.begin(); it != std::prev(f.end()); ++it ){
            data.at(it->y).at(it->x) = '+';
            it->move();
            while( it->x != std::next(it)->x || it->y != std::next(it)->y){
                data.at(it->y).at(it->x) = (it->direction == UP || it->direction == DOWN ) ? '|' : '-';
                it->move();
            }
        }
        for( auto & str : data ){
            str.erase(str.find_last_not_of(' ') + 1);
        }
        return join("\n", data);
    }
};

// external

Data break_piece(const std::string &shape) {
    Data input = split_lines(shape);
    std::vector<Figure> figures;
    for( auto row = 0; row < input.size(); row++ ){
        for( auto column = 0; column < input.at(row).size(); column++ ){
            if( input.at(row).at(column) == '+' ){
                if( row + 1 < input.size() && column < input.at(row+1).size() && input.at(row+1).at(column) != ' '
                        && column + 1 < input.at(row).size() && input.at(row).at(column+1) != ' ' )
                {
                    figures.push_back(worker::extract(input, row, column));
                }
            }
        }
    }
    Data output;
    for( const auto & f : worker::checkFigures(figures) ){
        output.push_back(worker::toASCII(f));
    }
    return output;
}

